// Copyright (c) 2007, Brian Duff

// See LICENSE for full license details.


package org.dubh.jdant;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;

import java.net.URL;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;


/**
 * Utilities.
 *
 * @author brian.duff@dubh.org
 */
public final class Util
{
  
  private static SAXParser _parser;
  
  private Util()
  {
  }
  
  private static SAXParserFactory createParserFactory()
  {
    // Prefer oracle's parser, since it's fast.
    try
    {
      Class c = Class.forName( "oracle.xml.jaxp.JXSAXParserFactory" );
      return (SAXParserFactory) c.newInstance();
    }
    catch ( Exception e )
    {
      return SAXParserFactory.newInstance();
    }
  }
  
  static SAXParser newSAXParser()
  {
    if ( _parser != null ) return _parser;
    try
    {
      SAXParserFactory factory = createParserFactory();
      factory.setValidating( false );
      factory.setNamespaceAware( true );

      _parser = factory.newSAXParser();
    }
    catch ( ParserConfigurationException e )
    {
      throw new IllegalStateException( e );
    }
    catch ( SAXException e )
    {
      throw new IllegalStateException( e );
    }
    return _parser;
  }
  
  
  /**
   * Normalizes a file, removing any ".." in the path. Unlike File.getCanonicalFile(),
   * this method intentionally does not resolve symbolic links.
   * 
   * @param file
   * @return
   */
  public static File normalize( File file )
  {
    String path = file.getPath();
    StringTokenizer parts = new StringTokenizer( path, "/" + File.separatorChar );

    List resultParts = new ArrayList();
    while ( parts.hasMoreTokens() )
    {
      String token = parts.nextToken();
      if ( "..".equals( token ) )
      {
        if ( !resultParts.isEmpty() )
        {
          resultParts.remove( resultParts.size() - 1 );
        }
      }
      else if ( ".".equals( token ) )
      {
        // skip.
      }
      else
      {
        resultParts.add( token );
      }
    }

    StringBuffer result = new StringBuffer();
    for ( Iterator i = resultParts.iterator(); i.hasNext(); )
    {
      String part = (String) i.next();
      result.append( part );
      if ( i.hasNext() )
      {
        result.append( File.separator );
      }
    }

    // Make sure path starts with a / if it's not windows...
    if ( result.indexOf( ":" ) == -1 )
    {
      result.insert( 0, File.separator );
    }

    return new File( result.toString() );
  }  
  
  /**
   * Util method which will always return an empty list rather than null.
   * 
   * @param list a list, which may be null.
   * @return a list. Either <tt>list</tt> if it is not null, or an empty
   *    list.
   */
  public static <T> List<T> safeList( List<T> list )
  {
    return list == null ? Collections.<T>emptyList() : list;
  }
  
  /**
   * Gets a list of files from a hash structure.
   * 
   * @param hash a hash structure.
   * @param listKey the key of the list structure.
   * @param itemUrlKey the key of each url within the list structure.
   * 
   * @return an unmodifiable list of Files.
   */
  public static List<File> files( ROHashStructure hash, String listKey, 
    String itemUrlKey )
  {
    List<ROHashStructure> list = hash.getListOfHash( listKey );
    if ( list == null ) return Collections.emptyList();

    List<File> result = new ArrayList<File>( list.size() );
    for ( ROHashStructure item : list )
    {
      URL url = item.getURL( itemUrlKey );
      if ( url == null ) continue;
      result.add( Util.normalize( new File( url.getPath() ) ) );
    }

    return Collections.unmodifiableList( result );
  }
  
  /**
   * Converts a collection of URLs to a List of files.
   * 
   * @param urls a collection of URLs.
   * @return a list of Files.
   */
  public static List<File> toListOfFiles( Collection<URL> urls )
  {
    if ( urls == null ) return Collections.emptyList();
    List<File> files = new ArrayList<File>( urls.size() );
    for ( URL url : urls )
    {
      files.add( Util.normalize( new File( url.getPath() ) ) );
    }
    return Collections.unmodifiableList( files );
  }
  
  /**
   * Close a Closeable.
   * 
   * @param c
   */
  public static void close( Closeable c )
  {
    if ( c == null ) return;
    
    try 
    { 
      c.close(); 
    } 
    catch ( IOException e ) 
    {
      throw new IllegalStateException( e );
    }
  }
    
  /**
   * Create a relative path, given two files. 
   */
  static String toRelativePath( File baseFile, File file  )
  {
    String filePath = file.getPath();
    String basePath = baseFile.getPath();

    String[] filePathBits = filePath.split( "\\" + File.separatorChar );
    String[] basePathBits = basePath.split( "\\" + File.separatorChar);
    
    // Eliminate common root.
    int commonRootEnd = -1;
    for ( int i=0; i < basePathBits.length; i++ )
    {
      // No common root?
      if ( i >= filePathBits.length ) return basePath;
      
      if ( !filePathBits[i].equals( basePathBits[i] ) )
      {
        commonRootEnd = i;
        break;
      }
    }
    
    // Exact same path. Return just the filename
    if ( commonRootEnd == -1 ) return baseFile.getName();
    
    StringBuilder result = new StringBuilder();
    
    // Append enough .. to the result to get from the lib file to the common
    // root.
    for ( int i=0; i < (basePathBits.length - commonRootEnd - 1); i++ )
      result.append( "../" );
  
    
    // Now append the remaining parts of the file path.
    for ( int i=commonRootEnd; i < filePathBits.length; i++ )
    {
      result.append( filePathBits[i] );
      if ( i < filePathBits.length-1 ) result.append( '/' );
    }
    
    return result.toString(); 
    
  }
}
